import { observe } from "./observe";
import Dep from "./observe/dep";
import Watcher, { nextTick } from './observe/watcher';

export function initState(vm) {
    // 获取所有选项 
    const opts = vm.$options;

    // 初始化 data
    if (opts.data) {
        initData(vm);
    }

    // 初始化 computed
    if (opts.computed) {
        initComputed(vm);
    }

    // 初始化 watch
    if (opts.watch) {
        initWatch(vm);
    }
}

export function initStateMixin(Vue) {
    // 扩展 nextTick 方法
    Vue.prototype.$nextTick = nextTick;
    
    // 扩展 $watch 方法
    Vue.prototype.$watch = function (expOrFn, cb, options = { deep: false, immediate: false }) {
        // 标识为用户创建的 watcher
        options.user = true;
        // 创建一个 watcher
        new Watcher(this, expOrFn, options, cb);
    }
}

/*
    初始化 data
*/
function proxy(vm, sourceKey) {
    // 循环代理每个属性
    Object.keys(vm[sourceKey]).forEach(key => {
        Object.defineProperty(vm, key, {
            get() {
                return vm[sourceKey][key];
            },
            set(newVal) {
                if (vm[sourceKey][key] === newVal) return;
                vm[sourceKey][key] = newVal;
            }
        })
    })
}
function initData(vm) {
    // 获取用户传入的 data 选项
    let data = vm.$options.data;

    // data 可能是函数，也可能是对象。如果是函数，则执行函数，不是则返回 data
    data = typeof data === 'function' ? data.call(vm) : data;

    // 将数据挂载到 vm 上
    vm._data = data;

    // 将 vm._data 用 vm 来代理
    proxy(vm, '_data');

    // 对数据进行劫持，通过 Object.defineProperty 来实现
    observe(data);
}

/*
    初始化 computed

    计算属性传参方式
        字符串：直接指定属性名
        对象：指定属性名和 getter 和 setter
        函数：指定 getter
    
    计算属性是在取值的时候去执行，而不是在初始化的时候
    所以在创建 watcher 时，添加一个 lazy 标识符进行区分为懒执行
    Vue2中的计算属性不会去收集依赖，只会让自己的依赖属性去收集依赖
*/
function defineComputed(target, key, expOrFn) {
    // 获取 setter
    const setter = expOrFn.set || function () { };
    // 数据劫持
    Object.defineProperty(target, key, {
        // 创建一个计算属性 getter
        get: createComputeGetter(key),
        set: setter
    })
}
function createComputeGetter(key) {
    // 返回一个 getter 函数，调用该函数则触发计算属性的 getter 进行取值
    return function () {

        /*
            这里的 this 指向的是当前调用的计算属性
            计算属性挂载在 vm 上，如：vm.xxx.getter()
            this 指向 vm，所以可以访问 vm 上的 _computed 属性
        */

        // 获取到对应计算属性的 watcher
        const watcher = this._computedWatchers[key];

        // 判断当前的计算属性的值是否是脏的
        if (watcher.options.dirty) {
            watcher.evaluate();
        }

        // 如果计算属性出栈后，当前 Dep.target 不为空，则让计算属性 watcher 的属性也去收集上层的 watcher
        if (Dep.target) {
            watcher.depend();
        }

        // 并将计算属性 watcher 上最新的值进行返回
        return watcher.value;
    }
}
function initComputed(vm) {
    // 获取传入的 computed 选项
    let computed = vm.$options.computed;

    // 存储计算属性 watcher，并将所有计算属性 watcher 挂载到 vm 上
    let watchers = vm._computedWatchers = {};

    // 循环遍历 computed 选项
    for (let key in computed) {

        // 获取每一项
        let expOrFn = computed[key];

        // 获取 getter
        const get = typeof expOrFn === 'function' ? expOrFn : expOrFn.get;

        // 创建一个计算属性 watcher, 并且存储到对应 key 的 watcher 到 watchers 中
        watchers[key] = new Watcher(vm, get, { lazy: true, dirty: true })

        // 创建计算属性
        defineComputed(vm, key, expOrFn);
    }
}

/*
    初始化 watch
     watch传参方式: 字符串、对象、 函数、数组
*/
function createWatcher(vm, key, handler) {
    // 选项
    const options = {
        deep: false,
        immediate: false,
    }

    // 处理 handler 是字符串
    if (typeof handler === 'string') {
        handler = vm[handler];
    }

    // 处理 handler 是对象
    if (Object.prototype.toString.call(handler) === '[object Object]') {
        // 获取选项
        options.deep = handler.deep || false;
        options.immediate = handler.immediate || false;

        if (typeof handler.handler === 'string') {
            // 处理 handler 是字符串
            handler = vm[handler.handler];
        } else if (typeof handler.handler === 'function') {
            // 处理 handler 是函数
            handler = handler.handler;
        } else {
            // 处理 handler 除字符串和函数以外的其他情况
        }
    }

    return vm.$watch(key, handler, options);
}
function initWatch(vm) {
    // 获取传入的 watch 选项
    let watch = vm.$options.watch;

    // 循环遍历 watch 选项
    for (let key in watch) {
        // 获取对应的 handler
        const handler = watch[key];

        if (Array.isArray(handler)) {
            // 处理 handler 是数组
            handler.forEach(h => createWatcher(vm, key, h));
        } else {
            // 处理 handler 是数组以外的其他情况
            createWatcher(vm, key, handler);
        }
    }
}